#include "Grid.h"
#include "stdio.h"

using namespace glm;

int Grid::getIndex(int i, int tx, int ty) {
    switch (i) {
        case 0:
            return tx;
        case 1:
            return tx + (res + 1);
        case 2:
            return tx + res + (res + 1);
        case 3:
            return tx + res;
        case 4:
            return tx + 2 * res * (res + 1) + (res + 1) * (res + 1);
        case 5:
            return tx + 2 * res * (res + 1) + (res + 1) * (res + 1) + (res + 1);
        case 6:
            return tx + 2 * res * (res + 1) + (res + 1) * (res + 1) +
             res + (res + 1);
        case 7:
            return tx + 2 * res * (res + 1) + (res + 1) * (res + 1) + res;
        case 8:
            return ty;
        case 9:
            return ty + 1;
        case 10:
            return ty + res + 2;
        case 11:
            return ty + res + 1;
        default:
            return tx;
    }
}

void Vertex::addNeighbor(Vertex *v) {
    for (int i = 0; i < neighbor_count; i++) {
        if (neighbors[i] == v)
            return;
    }

    neighbors[neighbor_count] = v;
    neighbor_count++;
}

vec3 Grid::getTri(int val, vec3 pos, vec3 delta) {
    vec3 ret;
    switch (val) {
        case 0:
            ret = vec3(pos.x + delta.x / 2.f, pos.y, pos.z);
            break;
        case 1:
            ret = vec3(pos.x + delta.x, pos.y, pos.z + delta.z / 2.f);
            break;
        case 2:
            ret = vec3(pos.x + delta.x / 2.f, pos.y, pos.z + delta.z);
            break;
        case 3:
            ret = vec3(pos.x, pos.y, pos.z + delta.z / 2.f);
            break;
        case 4:
            ret = vec3(pos.x + delta.x / 2.f, pos.y + delta.y, pos.z);
            break;
        case 5:
            ret = vec3(pos.x + delta.x, pos.y + delta.y, pos.z + delta.z / 2.f);
            break;
        case 6:
            ret = vec3(pos.x + delta.x / 2.f, pos.y + delta.y, pos.z + delta.z);
            break;
        case 7:
            ret = vec3(pos.x, pos.y + delta.y, pos.z + delta.z / 2.f);
            break;
        case 8:
            ret = vec3(pos.x, pos.y + delta.y / 2.f, pos.z);
            break;
        case 9:
            ret = vec3(pos.x + delta.x, pos.y + delta.y / 2.f, pos.z);
            break;
        case 10:
            ret = vec3(pos.x + delta.x, pos.y + delta.y / 2.f, pos.z + delta.z);
            break;
        case 11:
            ret = vec3(pos.x, pos.y + delta.y / 2.f, pos.z + delta.z);
            break;
        default:
            ret = pos;
            break;
    }

    return ret;
}

int getGridIndex(int index, int boxIndex, int res) {
    switch (boxIndex) {
        case 0:
            return index;
        case 1:
            return index + 1;
        case 2:
            return index + res + 2;
        case 3:
            return index + res + 1;
        case 4:
            return index + (res + 1) * (res + 1);
        case 5:
            return index + (res + 1) * (res + 1) + 1;
        case 6:
            return index + (res + 1) * (res + 1) + res + 2;
        case 7:
            return index + (res + 1) * (res + 1) + res + 1;
    }
}

void Grid::generateVertices() {
    int x, y, z, index, tri_index;
    x = y = z = index = 0;
    GBox *box;
    float xl, yl, zl;
    int mask;
    HNode *temp;
    bool btemp;
    int *tri_val;
    int tx, ty;
    vec3 pos;

    tx = 0;
    ty = 2 * (res + 1) * (res);

    assert(edge_points != NULL);
    while (index < res * res * res) {
        mask = 1;
        pos = min;

        pos.x += x * delta.x;
        pos.y += y * delta.y;
        pos.z += z * delta.z;

        int val = 0;
        //printf("%d out of %d\n", index, res * res *res);
        for (int i = 0; i < 8; i++) {
           // ip = getGridIndex(index, i, res);
            xl = min.x + x * delta.x;
            yl = min.y + y * delta.y;
            zl = min.z + z * delta.z;

            if (i % 4 == 1 || i % 4 == 2)
                xl += delta.x;
            if (i % 4 >= 2)
                zl += delta.z;
            if (i >= 4)
                yl += delta.y;

            //temp = hash->lookUp(xl, yl, zl);
            btemp = hash->exists(xl, yl, zl);

            if (btemp) {
                val |= mask;
           //     delete temp;
            }

            mask = mask << 1;
        }

        (&boxes[index])->verts = val;
        tri_val = triTable[val];
        tri_index = 0;
        while (tri_index < 16 && tri_val[tri_index] != -1) {
            int i1, i2, i3;
            Vertex *vert1, *vert2, *vert3;

            i1 = getIndex(tri_val[tri_index], tx, ty);
            i2 = getIndex(tri_val[tri_index + 1], tx, ty);
            i3 = getIndex(tri_val[tri_index + 2], tx, ty);
             
            vert1 = &edge_points[i1];
            vert2 = &edge_points[i2];
            vert3 = &edge_points[i3];
            vert1->pos = getTri(tri_val[tri_index], pos, delta);
            vert2->pos = getTri(tri_val[tri_index + 1], pos, delta);
            vert3->pos = getTri(tri_val[tri_index + 2], pos, delta);
        /*    vert1->min = vert1->pos - vec3(2.f) * hash->dist;
            vert1->max = vert1->pos + vec3(2.f) * hash->dist;
            vert2->min = vert2->pos - vec3(2.f) * hash->dist;
            vert2->max = vert2->pos + vec3(2.f) * hash->dist;
            vert3->min = vert3->pos - vec3(2.f) * hash->dist;
            vert3->max = vert3->pos + vec3(2.f) * hash->dist;*/

            vert1->active = true;
            vert2->active = true;
            vert3->active = true;

            vert1->addNeighbor(vert2);
            vert1->addNeighbor(vert3);
            vert2->addNeighbor(vert1);
            vert2->addNeighbor(vert3);
            vert3->addNeighbor(vert2);
            vert3->addNeighbor(vert1);

            tri_index += 3;
        }

        index++;
        if (index % (res * res) == 0) {
            x = z = 0;
            y++;
            tx += (res + 1) * (res + 1) + 2 * res + 2;
            ty += (2 * res + 1) * (res + 1) + 2;
        }
        else if (index % res == 0) {
            x = 0;
            z++;
            tx += res + 2;
            ty += 2;
        }
        else {
            x++;
            tx++;
            ty++;
        }
    }
}

vec3 avg(Vertex *v) {
    vec3 ret = vec3(0.f);
    for (int i = 0; i < v->neighbor_count; i++) {
        ret += v->neighbors[i]->pos;
    }

    return (ret / ((float) v->neighbor_count));
}

void Grid::smoothVertices(int rounds) {
    Vertex *vert;
    for (int i = 0; i < rounds; i++) {
        for (int j = 0; j < (res * (res + 1) * 3 * (res + 1)); j++) {
            vert = &edge_points[j];
            if (vert->active)
                vert->next_pos = avg(vert);
        }
        for (int j = 0; j < (res * (res + 1) * 3 * (res + 1)); j++) {
            vert = &edge_points[j];
            if (vert->active) {
             // && inside(vert->next_pos, vert->min, vert->max)) {
                    vert->pos = vert->next_pos;
            }
        }
    }
}
